package edu.gatech.spacetraderteam3.view;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import edu.gatech.spacetraderteam3.debug.Debug;
import edu.gatech.spacetraderteam3.model.GameState;

/**
 * A pop up window in the GUI
 * 
 * @author Alexander Ikonomidis
 * @version 1.0
 */
public class SGGWindow extends SGGComponent {
    public enum Alignment {
        TOP_LEFT,
        TOP_RIGHT,
        BOTTOM_LEFT,
        BOTTOM_RIGHT,
        CENTER;
    }

    /**
     * The x and y locations of the window
     */
    protected final int oldOriginX, oldOriginY;

    /**
     * The window's skin
     */
    protected static Assets.WindowSkin Skin = Assets.WindowSkin.SPACE;

    /**
     * The window's height, width, and pointer attributes
     */
    protected int cHeight, cWidth, pHeight, pWidth, sHeight, sWidth, tHeight, tWidth,
            pointOff;

    /**
     * window positioned relative to top left by default
     */
    private Alignment alignment = Alignment.TOP_LEFT;

    /**
     * Alignment of the pointer of the window
     */
    protected Direction pointSide = Direction.SOUTH; // TODO: reduce variables

    /**
     * The images for each window
     */
    protected static BufferedImage Corner, Point, Side, Tile;

    /**
     * used to backup initial position
     */
    protected Color fillColor = Color.white;

    /**
     * Boolean values that dictate different attributes of the window
     */
    protected boolean hasPoint = false, isShowingContainer = true;

    /**
     * The component focused in the window
     */
    private SGGComponent focusedComponent;

    /**
     * The components in this window
     */
    private final List<SGGComponent> components;

    /**
     * The delegate that handles inputs into this window from the user
     */
    private final InputDelegate inputDelegate;

    /**
     * Constructor for the SGGWindow
     * 
     * @param xPos
     * @param yPos
     * @param wid
     * @param high
     * @param windowGroup
     */
    public SGGWindow(int xPos, int yPos, int wid, int high, GameState windowGroup) {
        super(wid, high);
        setOffset(xPos, yPos);
        inputDelegate = new InputDelegate(windowGroup);
        SGGWM.registerSGGWindow(inputDelegate, windowGroup);
        components = new ArrayList<SGGComponent>();

        Corner = Skin.corner();
        cWidth = Corner.getWidth();
        cHeight = Corner.getHeight();

        Point = Skin.point();
        pWidth = Point.getWidth();
        pHeight = Point.getHeight();

        Side = Skin.side();
        sWidth = Side.getWidth();
        sHeight = Side.getHeight();

        Tile = Skin.tile();
        tWidth = Tile.getWidth();
        tHeight = Tile.getHeight();

        oldOriginX = getX();
        oldOriginY = getY();
        pointOff = 0;
    }

    /**
     * Tells this window whether or not to have a point
     * 
     * @param hasPoint
     */
    public void hasPoint(boolean hasPoint) {
        this.hasPoint = hasPoint;
    }

    /**
     * Sets the point side on the window
     * 
     * @param direction
     */
    public void setPointSide(SGGComponent.Direction direction) {
        pointSide = direction;
    }

    /**
     * Adds a component to this window
     * 
     * @param sggc
     * @param offX
     * @param offY
     */
    public void add(SGGComponent sggc, int offX, int offY) { // TODO: Change to use layout

                                        // component static across windows. Change that.
        sggc.setOffset(offX, offY);
        components.add(sggc);
    }

    /**
     * Removes a component from this window
     * 
     * @param sggc
     */
    public void remove(SGGComponent sggc) {
        components.remove(sggc);
    }

    /**
     * Sests the alignment of this window in the overall view
     * 
     * @param alignment
     */
    public void setAlignment(Alignment alignment) {
        this.alignment = alignment;
    }

    /**
     * Paints this window onto the screen
     * 
     * @param g
     *            - the graphics object
     */
    public void doPaint(Graphics2D g) {
        final int wid = (int) SGGWM.getWindowSize().getWidth();
        final int hei = (int) SGGWM.getWindowSize().getHeight();
        switch (alignment) {
            case TOP_LEFT:
                setOrigin(oldOriginX, oldOriginY);
                break;
            case TOP_RIGHT:
                setOrigin(wid - getWidth() - oldOriginX, oldOriginY);
                break;
            case BOTTOM_LEFT:
                setOffset(oldOriginX, hei - getHeight() - oldOriginY);
                break;
            case BOTTOM_RIGHT:
                setOrigin(wid - getWidth() - oldOriginX, hei - getHeight() - oldOriginY);
                break;
            case CENTER:
                setOrigin(wid / 2 - getWidth() / 2, hei / 2 - getHeight() / 2);
                break;
            default:
                Debug.write("Well, that must be because... Actually, I have no idea why that is.",
                            2);
                break;
        }
        if (isShowingContainer) {
            /* Some handy numbers */
            final int vertSideNum = (getHeight() - 90) / sHeight
                    + Math.min(1, (getHeight() - 90) % sHeight);
            final int horizSideNum = (getWidth() - 90) / sWidth
                    + Math.min(1, (getWidth() - 90) % sWidth);

            g.translate(getX(), getY());
            /* Fill */
            for (int yoff = 1; yoff <= vertSideNum; yoff++) {
                for (int xoff = 1; xoff <= horizSideNum; xoff++) {
                    g.drawImage(Tile, xoff * tWidth, yoff * tHeight, null);
                }
            }

            /* Top-left corner */
            g.drawImage(Corner, 0, 0, null);

            /* Left side and bottom-left corner */
            for (int off = 1; off <= vertSideNum; off++) {
                g.drawImage(Side, 0, (off * sHeight), null);
            }
            g.rotate(-Math.PI / 2);
            g.drawImage(Corner, -getHeight(), 0, null);
            g.rotate(Math.PI / 2);

            /* Top and top-right corner */
            g.rotate(Math.PI / 2);
            for (int off = 2; off <= horizSideNum + 1; off++) {
                g.drawImage(Side, 0, -off * sHeight, null);
            }
            g.drawImage(Corner, 0, -getWidth(), null);
            g.rotate(-Math.PI / 2);

            /* Bottom */
            g.rotate(-Math.PI / 2);
            for (int off = 1; off <= horizSideNum; off++) {
                g.drawImage(Side, -getHeight(), off * sHeight, null);
            }
            g.rotate(Math.PI / 2);

            /* Right side and bottom-right corner */
            g.rotate(Math.PI);
            for (int off = 1; off <= vertSideNum; off++) {
                g.drawImage(Side, -getWidth(), -(off * sHeight) - sHeight, null);
            }
            g.drawImage(Corner, -getWidth(), -getHeight(), null);
            g.rotate(-Math.PI);

            if (hasPoint) {
                int offX = 0, offY = 0;
                double theta;
                switch (pointSide) {
                    case NORTH:
                        theta = Math.PI;
                        offX = -getWidth() / 2 - Corner.getWidth() / 2 - pointOff;
                        offY = -Corner.getHeight() / 8;
                        break;
                    case EAST:
                        theta = -Math.PI / 2;
                        offX = -getHeight() / 2 - Corner.getWidth() / 2;
                        offY = getWidth() - Corner.getHeight() / 8 + pointOff;
                        break;
                    case WEST:
                        theta = Math.PI / 2;
                        offX = getWidth() / 2;
                        offY = -Corner.getHeight() / 8 - pointOff;
                        break;
                    default:
                        theta = 0;
                        offX = getWidth() / 2 - Corner.getWidth() / 2 + pointOff;
                        offY = getHeight() - Corner.getHeight() / 8;
                }
                g.rotate(theta);
                g.drawImage(Point, offX, offY, null);
                g.rotate(-theta);
            }

            g.translate(-getX(), -getY());
        }
        for (int i = components.size() - 1; i >= 0; i--) {
            components.get(i).setOrigin(getX(), getY());
            // c.setColor(new Color(200, 200, 200));
            components.get(i).paint(g);
        }
    }

    /**
     * Changes whether the container is being shown or not
     * 
     * @param isShowingContainer
     */
    public void showContainer(boolean isShowingContainer) {
        this.isShowingContainer = isShowingContainer;
    }

    /**
     * Tells this window to lose its focus in the game
     */
    protected void loseFocus() {
        if (focusedComponent != null) {
            focusedComponent.loseFocus();
        }
        focusedComponent = null;
    }

    /**
     * Disposes the window and hides its visibility
     */
    public void dispose() {
        SGGWM.disposeSGGWindow(inputDelegate);
        setVisible(false);
    }

    /**
     * Handles the input taken in by the GUI's components
     * 
     * @author Alexander Ikonomidis
     * @version 1.0
     */
    protected class InputDelegate {
        /**
         * The group of windows the input delegate is currently
         * communicating with
         */
        private final GameState windowGroup;

        /**
         * A boolean that indicates whether a component
         * recieved a click command
         */
        private boolean componentRecievedClick = false;

        /**
         * sets the windowGroup for this window
         * 
         * @param windowGroup
         */
        protected InputDelegate(GameState windowGroup) {
            this.windowGroup = windowGroup;
        }

        /**
         * response to a mouse event on a window
         * 
         * @param m
         */
        public void doClick(MouseEvent m) {
            componentRecievedClick = false;
            for (SGGComponent sggc : components) {
                if (sggc.contains(m.getPoint())) {
                    sggc.doClick(m.getPoint());
                    if (focusedComponent != sggc) {
                        if (focusedComponent != null) {
                            Debug.write("Losing focus: " + focusedComponent, 2);
                            focusedComponent.loseFocus(); // Component doesn't lose focus
                                                          // unless another component is
                                                          // clicked, not whitespace
                        }
                        focusedComponent = sggc;
                        Debug.write("Focused Component is now: " + sggc.getClass(), 2);
                    }
                    componentRecievedClick = true;
                    return;
                }
            }
            if (focusedComponent != null) {
                focusedComponent.loseFocus();
            }
            focusedComponent = null;
        }

        /**
         * Allows the player to drag a window
         * 
         * @param m
         */
        public void doDrag(MouseEvent m) {
            if (focusedComponent != null) {
                focusedComponent.doClick(m.getPoint());
            }
        }

        /**
         * Releases a player from dragging a window
         */
        public void doRelease() {
            if (componentRecievedClick && focusedComponent != null) {
                focusedComponent.doRelease();
            }
        }

        /**
         * Responds to a key event
         * 
         * @param k
         */
        public void doKey(KeyEvent k) {
            if (focusedComponent != null) {
                focusedComponent.doKey(k);
            }
        }

        /**
         * Removes focus from this window
         * 
         * @param p
         */
        public void loseFocus(Point p) {
            if (focusedComponent != null) {
                if (focusedComponent.contains(p)) {
                    focusedComponent.doClick(p);
                }
                focusedComponent.loseFocus();
            }
            SGGWindow.this.loseFocus();
        }

        /**
         * @return the bounds of this window
         */
        public Rectangle getBounds() {
            return bound;
        }

        /**
         * @param point
         * @return whether this window contains a certain point
         */
        public boolean contains(Point point) {
            return SGGWindow.this.contains(point);
        }

        /**
         * @return the window group of this window
         */
        public GameState getWindowGroup() {
            return windowGroup;
        }

        /**
         * @return this is an input delegate for a window
         */
        public String toString() {
            return "This is an input delegate window.";
        }
    }

    /**
     * Sets the offset of the point of this window
     * 
     * @param pointOff
     */
    public void setPointOffset(int pointOff) {
        this.pointOff = pointOff;
    }

    /**
     * sets the gametate of this window
     * 
     * @param gameState
     */
    protected void setListeningMode(GameState gameState) {
        SGGWM.changeRegistration(inputDelegate, gameState);
    }

    /**
     * @return this is a window
     */
    public String toString() {
        return "This is a window.";
    }
}